{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "toc": true
   },
   "source": [
    "<h1>Table of Contents<span class=\"tocSkip\"></span></h1>\n",
    "<div class=\"toc\" style=\"margin-top: 1em;\"><ul class=\"toc-item\"></ul></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Saving TF Models with SavedModel for TF Serving <a class=\"tocSkip\">"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NumPy:1.13.3\n",
      "Pandas:0.21.0\n",
      "Matplotlib:2.1.0\n",
      "TensorFlow:1.4.0\n",
      "Keras:2.0.9\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using TensorFlow backend.\n"
     ]
    }
   ],
   "source": [
    "import math\n",
    "import os\n",
    "\n",
    "import numpy as np\n",
    "np.random.seed(123)\n",
    "print(\"NumPy:{}\".format(np.__version__))\n",
    "\n",
    "import tensorflow as tf\n",
    "tf.set_random_seed(123)\n",
    "print(\"TensorFlow:{}\".format(tf.__version__))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "DATASETSLIB_HOME = os.path.expanduser('~/dl-ts/datasetslib')\n",
    "import sys\n",
    "if not DATASETSLIB_HOME in sys.path:\n",
    "    sys.path.append(DATASETSLIB_HOME)\n",
    "%reload_ext autoreload\n",
    "%autoreload 2\n",
    "import datasetslib\n",
    "\n",
    "from datasetslib import util as dsu\n",
    "datasetslib.datasets_root = os.path.join(os.path.expanduser('~'),'datasets')\n",
    "models_root = os.path.join(os.path.expanduser('~'),'models')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Serving Model in TensorFlow"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Saving model with SavedModel"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Restart kernel to run the flag setting again\n",
    "#tf.flags.DEFINE_integer('model_version', 1, 'version number of the model.')\n",
    "model_name = 'mnist'\n",
    "model_version = '1'\n",
    "model_dir = os.path.join(models_root,model_name,model_version)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Extracting /home/armando/datasets/mnist/train-images-idx3-ubyte.gz\n",
      "Extracting /home/armando/datasets/mnist/train-labels-idx1-ubyte.gz\n",
      "Extracting /home/armando/datasets/mnist/t10k-images-idx3-ubyte.gz\n",
      "Extracting /home/armando/datasets/mnist/t10k-labels-idx1-ubyte.gz\n"
     ]
    }
   ],
   "source": [
    "# get the MNIST Data\n",
    "\n",
    "from tensorflow.examples.tutorials.mnist import input_data\n",
    "mnist = input_data.read_data_sets(os.path.join(datasetslib.datasets_root,'mnist'), one_hot=True)\n",
    "\n",
    "x_train = mnist.train.images\n",
    "x_test = mnist.test.images\n",
    "y_train = mnist.train.labels\n",
    "y_test = mnist.test.labels\n",
    "\n",
    "# parameters\n",
    "pixel_size = 28 \n",
    "num_outputs = 10  # 0-9 digits\n",
    "num_inputs = 784  # total pixels"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mlp(x, num_inputs, num_outputs,num_layers,num_neurons):\n",
    "    w=[]\n",
    "    b=[]\n",
    "    for i in range(num_layers):\n",
    "        # weights\n",
    "        w.append(tf.Variable(tf.random_normal( \\\n",
    "                              [num_inputs if i==0 else num_neurons[i-1], \\\n",
    "                               num_neurons[i]]), \\\n",
    "                             name=\"w_{0:04d}\".format(i) \\\n",
    "                            ) \\\n",
    "                ) \n",
    "        # biases\n",
    "        b.append(tf.Variable(tf.random_normal( \\\n",
    "                              [num_neurons[i]]), \\\n",
    "                             name=\"b_{0:04d}\".format(i) \\\n",
    "                            ) \\\n",
    "                )                   \n",
    "    w.append(tf.Variable(tf.random_normal(\n",
    "                          [num_neurons[num_layers-1] if num_layers > 0 else num_inputs,\n",
    "                           num_outputs]),name=\"w_out\"))\n",
    "    b.append(tf.Variable(tf.random_normal([num_outputs]),name=\"b_out\"))\n",
    "    \n",
    "    # x is input layer\n",
    "    layer = x\n",
    "    # add hidden layers\n",
    "    for i in range(num_layers):\n",
    "        layer = tf.nn.relu(tf.matmul(layer, w[i]) + b[i])\n",
    "    # add output layer\n",
    "    layer = tf.matmul(layer, w[num_layers]) + b[num_layers]\n",
    "    model = layer\n",
    "    probs = tf.nn.softmax(model)\n",
    "    \n",
    "    return model,probs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "tf.reset_default_graph()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# input images\n",
    "serialized_tf_example = tf.placeholder(tf.string, name='tf_example')\n",
    "feature_configs = {'x': tf.FixedLenFeature(shape=[784], dtype=tf.float32),}\n",
    "tf_example = tf.parse_example(serialized_tf_example, feature_configs)\n",
    "x_p = tf.identity(tf_example['x'], name='x_p')  # use tf.identity() to assign name\n",
    "\n",
    "# target output\n",
    "y_p = tf.placeholder(dtype=tf.float32, name=\"y_p\", shape=[None, num_outputs]) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 0000   loss = 58.275672\n",
      "epoch: 0001   loss = 12.980265\n",
      "epoch: 0002   loss = 7.946372\n",
      "epoch: 0003   loss = 5.640132\n",
      "epoch: 0004   loss = 4.194648\n",
      "epoch: 0005   loss = 3.293190\n",
      "epoch: 0006   loss = 2.660178\n",
      "epoch: 0007   loss = 2.217029\n",
      "epoch: 0008   loss = 1.806662\n",
      "epoch: 0009   loss = 1.527517\n",
      "epoch: 0010   loss = 1.308219\n",
      "epoch: 0011   loss = 1.115864\n",
      "epoch: 0012   loss = 0.957490\n",
      "epoch: 0013   loss = 0.846164\n",
      "epoch: 0014   loss = 0.733891\n",
      "epoch: 0015   loss = 0.622274\n",
      "epoch: 0016   loss = 0.540578\n",
      "epoch: 0017   loss = 0.473720\n",
      "epoch: 0018   loss = 0.416551\n",
      "epoch: 0019   loss = 0.364625\n",
      "epoch: 0020   loss = 0.326988\n",
      "epoch: 0021   loss = 0.272674\n",
      "epoch: 0022   loss = 0.243471\n",
      "epoch: 0023   loss = 0.207583\n",
      "epoch: 0024   loss = 0.183325\n",
      "epoch: 0025   loss = 0.153376\n",
      "epoch: 0026   loss = 0.135717\n",
      "epoch: 0027   loss = 0.119947\n",
      "epoch: 0028   loss = 0.103426\n",
      "epoch: 0029   loss = 0.085863\n",
      "epoch: 0030   loss = 0.076752\n",
      "epoch: 0031   loss = 0.069757\n",
      "epoch: 0032   loss = 0.056827\n",
      "epoch: 0033   loss = 0.046720\n",
      "epoch: 0034   loss = 0.041128\n",
      "epoch: 0035   loss = 0.033346\n",
      "epoch: 0036   loss = 0.029719\n",
      "epoch: 0037   loss = 0.026037\n",
      "epoch: 0038   loss = 0.022546\n",
      "epoch: 0039   loss = 0.018932\n",
      "epoch: 0040   loss = 0.016667\n",
      "epoch: 0041   loss = 0.013022\n",
      "epoch: 0042   loss = 0.013127\n",
      "epoch: 0043   loss = 0.008736\n",
      "epoch: 0044   loss = 0.006944\n",
      "epoch: 0045   loss = 0.004991\n",
      "epoch: 0046   loss = 0.004136\n",
      "epoch: 0047   loss = 0.003154\n",
      "epoch: 0048   loss = 0.002946\n",
      "epoch: 0049   loss = 0.002317\n",
      "accuracy=0.92979997\n",
      "INFO:tensorflow:No assets to save.\n",
      "INFO:tensorflow:No assets to write.\n",
      "INFO:tensorflow:SavedModel written to: b'/home/armando/models/mnist/1/saved_model.pb'\n",
      "Go to folder where the notebooks are and run following command:\n",
      "tensorflow_model_server --port=9000 --model_name=mnist --model_base_path=/home/armando/models/mnist\n"
     ]
    }
   ],
   "source": [
    "num_layers = 2\n",
    "num_neurons = []\n",
    "for i in range(num_layers):\n",
    "    num_neurons.append(256)\n",
    "    \n",
    "learning_rate = 0.01\n",
    "n_epochs = 50\n",
    "batch_size = 100\n",
    "n_batches = mnist.train.num_examples//batch_size\n",
    "\n",
    "model,probs = mlp(x=x_p, \n",
    "            num_inputs=num_inputs, \n",
    "            num_outputs=num_outputs, \n",
    "            num_layers=num_layers, \n",
    "            num_neurons=num_neurons)\n",
    "\n",
    "# loss function\n",
    "#loss = tf.reduce_mean(-tf.reduce_sum(y * tf.log(model), axis=1))\n",
    "loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=model, labels=y_p))\n",
    "# optimizer function\n",
    "optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)\n",
    "\n",
    "train_op = optimizer.minimize(loss)\n",
    "\n",
    "predictions_check = tf.equal(tf.argmax(probs,1), tf.argmax(y_p,1))\n",
    "accuracy_function = tf.reduce_mean(tf.cast(predictions_check, tf.float32))\n",
    "\n",
    "values, indices = tf.nn.top_k(probs, 10)\n",
    "table = tf.contrib.lookup.index_to_string_table_from_tensor(\n",
    "  tf.constant([str(i) for i in range(10)]))\n",
    "prediction_classes = table.lookup(tf.to_int64(indices))\n",
    "\n",
    "with tf.Session() as tfs:\n",
    "    tfs.run(tf.global_variables_initializer())\n",
    "    for epoch in range(n_epochs):\n",
    "        epoch_loss = 0.0\n",
    "        for batch in range(n_batches):\n",
    "            x_batch, y_batch = mnist.train.next_batch(batch_size)\n",
    "            _,batch_loss = tfs.run([train_op,loss], feed_dict={x_p: x_batch, y_p: y_batch})\n",
    "            epoch_loss += batch_loss \n",
    "        average_loss = epoch_loss / n_batches\n",
    "        print(\"epoch: {0:04d}   loss = {1:0.6f}\".format(epoch,average_loss))\n",
    "    accuracy_score = tfs.run(accuracy_function, feed_dict={x_p: x_test, y_p: y_test })\n",
    "    print(\"accuracy={0:.8f}\".format(accuracy_score))\n",
    "\n",
    "    # save the model\n",
    "    \n",
    "    # definitions for saving the models\n",
    "    builder = tf.saved_model.builder.SavedModelBuilder(model_dir)\n",
    "\n",
    "    # build signature_def_map\n",
    "\n",
    "    classification_inputs = tf.saved_model.utils.build_tensor_info(\n",
    "      serialized_tf_example)\n",
    "    classification_outputs_classes = tf.saved_model.utils.build_tensor_info(\n",
    "      prediction_classes)\n",
    "    classification_outputs_scores = tf.saved_model.utils.build_tensor_info(values)\n",
    "\n",
    "    classification_signature = (\n",
    "      tf.saved_model.signature_def_utils.build_signature_def(\n",
    "          inputs={\n",
    "              tf.saved_model.signature_constants.CLASSIFY_INPUTS:\n",
    "                  classification_inputs\n",
    "          },\n",
    "          outputs={\n",
    "              tf.saved_model.signature_constants.CLASSIFY_OUTPUT_CLASSES:\n",
    "                  classification_outputs_classes,\n",
    "              tf.saved_model.signature_constants.CLASSIFY_OUTPUT_SCORES:\n",
    "                  classification_outputs_scores\n",
    "          },\n",
    "          method_name=tf.saved_model.signature_constants.CLASSIFY_METHOD_NAME))\n",
    "\n",
    "    tensor_info_x = tf.saved_model.utils.build_tensor_info(x_p)\n",
    "    tensor_info_y = tf.saved_model.utils.build_tensor_info(probs)\n",
    "\n",
    "    prediction_signature = (\n",
    "          tf.saved_model.signature_def_utils.build_signature_def(\n",
    "              inputs={'inputs': tensor_info_x},\n",
    "              outputs={'outputs': tensor_info_y},\n",
    "              method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME))\n",
    "\n",
    "    legacy_init_op = tf.group(tf.tables_initializer(), name='legacy_init_op')\n",
    "    builder.add_meta_graph_and_variables(\n",
    "      tfs, [tf.saved_model.tag_constants.SERVING],\n",
    "      signature_def_map={\n",
    "          'predict_images':\n",
    "              prediction_signature,\n",
    "          tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:\n",
    "              classification_signature,\n",
    "      },\n",
    "      legacy_init_op=legacy_init_op)\n",
    "    \n",
    "    builder.save()\n",
    "\n",
    "print('Run following command:')\n",
    "print('tensorflow_model_server --model_name=mnist --model_base_path={}'\n",
    "      .format(os.path.join(models_root,model_name)))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  },
  "toc": {
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": true,
   "toc_cell": true,
   "toc_position": {},
   "toc_section_display": "block",
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
